/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.mzk.oai.metadataRepository.metadata;

import cz.mzk.oai.metadataRepository.Tools;
import cz.mzk.oai.metadataRepository.tools.FileTools;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Iterator;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;

/**
 *
 * @author Martin Rehanek, Martin.Rehanek@gmail.com
 * TODO: nahradit org.marc4j.marc.Record
 */
public class MarcRecord {

    //private File inFile;
    private String tei5Url = null;
    private String controlfield008 = null;
    private String controlfieldFMT = null;
    protected String id = null;
    //TODO: zvazit, jestli bych nemel vsechny DataFieldy prevest na MultipleDataField
    private MultipleDataField datafield856 = null;
    private DataField datafield910 = null;
    private MultipleDataField datafield072 = null;
    private MultipleDataField datafield533 = null;
    private MultipleDataField datafield650 = null;
    private MultipleDataField datafield651 = null; //geografical location
    private MultipleDataField datafield655 = null;

    //private MultipleDataField datafield856 = null; //preview
    /**
     * Public constructor. Useful just for extending classes.
     */
    protected MarcRecord() {
    }

    /**
     *
     * @param inFile
     * @throws DocumentException if an error occurs during file parsing
     * @throws FileNotFoundException if file cannot be found
     */
    public MarcRecord(File inFile) throws DocumentException, FileNotFoundException {
        this.id = Tools.filenameToId(inFile.getName());
        Document doc = FileTools.loadDocument(inFile);
        init();
        loadDoc(doc);
    }

    public MarcRecord(Document doc, String id) {
        this.id = id;
        init();
        loadDoc(doc);
    }

    protected void init() {
        datafield072 = new MultipleDataField(1);
        datafield533 = new MultipleDataField(1);
        datafield650 = new MultipleDataField(2);
        datafield651 = new MultipleDataField(1);
        datafield655 = new MultipleDataField(2);
        datafield856 = new MultipleDataField(1);//DataField(3);
        datafield910 = new DataField(1);
    }

    /**
     * Looks into the metadata file (in XML representation of MARC21) and decides, if the item has been created before 1800 A.D.
     * http://www.loc.gov/marc/bibliographic/bd008a.html
     * @return true if it is sure, that item has been created before 1800 A.D. false if it hasn't or it can't be determined.
     * @throws MetadataFormatException if there is incorrect record in controllfield 008
     */
    public boolean isBefore1800() throws MetadataFormatException {
        String field008Content = getControlfield008();
        //System.out.println(field008Content);
        if (field008Content == null) {
            //System.err.println("field008Content == null");//TODO: spis zalogovat
            throw new MetadataFormatException("Missing controllfield 008");
        }
        char dateType = field008Content.toCharArray()[6];
        String date = null; //4 cisla, pokud nektere chybi, tak tam je 'u';
        //http://www.loc.gov/marc/bibliographic/bd008a.html
        //TODO: tady vsude testovat format a vyhodit vyjimku, pokud je to spatne kodovano
        //pri chytnuti vyjimky dany zaznam hodit do adresare error (misto before1800 nebo after1800)
        switch (dateType) {
            case 'b': //No dates given; B.C. date involved
                date = "0000";
                break;
            //jedno datum
            case 'e': //Detailed date
            case 's': //Single known date/probable date
            //zaznamy, ktere zacaly nekdy v minulosti a pokracuji do ted
            case 'c': //Continuing resource currently published
            case 'd': //Continuing resource ceased publication
            case 'i': //Inclusive date
            case 'k': //Range of years of bulk of collection
            case 'm': //Multiple dates
            case 'u': //Continuing resource status unknown
                date = field008Content.substring(7, 11);//first date
                break;
            case 'q': //Questionable date
                date = field008Content.substring(11, 15);//second date
                break;
            case 'n': //Dates unknown
            case '|': //No attemtp to code the date
            default:
                date = "9999";
                break;
            //don't know yet
            //case 'r': //Reprint/reissue date and original date
            //case 't': //Publication date and copyright date
        }
        //System.out.println("date:" + date);
        boolean result = false;
        try {
            result = (compare(date, "1801") == -1);
        } catch (IllegalArgumentException e) {
            //System.err.println("Incorrect content of controllfield 008");
            //System.err.println("id: " + this.getId());
            throw new MetadataFormatException("Incorrect content of controllfield 008");
        }
        return result;
    }

    public boolean hasPreview() {
        return !datafield856.empty();
    }

    private String getControlfield008() {
        return this.controlfield008;
    }

    /**
     * Returns true if there is a tei5 record for this item avaliable.
     * In that case getTei5Url() returns String with URL and not null
     */
    public boolean hasTei5Record() {
        return tei5Url != null;
    }

    /**
     * Returns url of record in tei5 or null if there is no such a record
     * @return
     */
    public String getTei5Url() {
        return tei5Url;
    }

    public String getId() {
        return id;
    }

    /**
     * Compares to years that are 4 character Strings. Years can contain 'u' on any position instead of number.
     * This means, that that value is unknown. For example: 19uu, uu84, 200u
     * @param first first year
     * @param second second year
     * @return 1 if first>second, -1 if first<second, 0 if first=second or it comparision cannot be decided (due to lack of information - 'u's)
     * @throws IllegalArgumentException if length of first or second is not 4 or one of them contains something else then numbers and 'u'
     */
    public static int compare(String first, String second) throws IllegalArgumentException {
        if (first.length() != 4 || second.length() != 4) {
            throw new IllegalArgumentException("illegal length of first or second");
        }

        byte[] firstBytes = first.getBytes();
        byte[] secondBytes = second.getBytes();
        for (int i = 0; i < 4; i++) {
            char firstChar = (char) (firstBytes[i] & 0xFF);
            char secondChar = (char) (secondBytes[i] & 0xFF);
            if (!(Character.isDigit(firstChar) || firstChar == 'u')) {
                //System.err.println("first: " + first);
                //System.err.println("second: " + second);
                throw new IllegalArgumentException("first[" + i + "]");
            }
            if (!(Character.isDigit(secondChar) || secondChar == 'u')) {
                throw new IllegalArgumentException("second[" + i + "]");
            }
            //both firstBytes[i] secondBytes[i] are numbers or 'u'
            if (firstBytes[i] == 'u' || secondBytes[i] == 'u') {
                return 0;
            } else {
                if (firstBytes[i] < secondBytes[i]) {
                    return -1;
                }
                if (firstBytes[i] > secondBytes[i]) {
                    return 1;
                }
            }

        }
        return 0;
    }

    protected void loadDoc(Document doc) {
        Element root = doc.getRootElement();
        //System.out.println("root: " + root.getName());
        for (Iterator<Element> i = root.elementIterator(); i.hasNext();) {
            Element element = (Element) i.next();
            analyzeElement(element);
        }
    }

    /**
     * Checks if the element is one of those, that we're interested in and if so, stores the value
     * @param element
     */
    private void analyzeElement(Element element) {
        if ("marc21:controlfield".equals(element.getQualifiedName())) {
            analyzeControlfieldElement(element);
        }
        //TODO: pokud je v '2' vzdy czenas, tak neuchovavat
        if ("marc21:datafield".equals(element.getQualifiedName())) {
            analyzeDatafieldElement(element);
        }
    }

    protected void analyzeControlfieldElement(Element element) {
        if ("008".equals(element.attributeValue("tag"))) {
            //System.out.println("008:" + element.getText());
            this.controlfield008 = element.getText();
        }
        if ("FMT".equals(element.attributeValue("tag"))) {
            //System.out.println("FMT:" + element.getText());
            this.controlfieldFMT = element.getText();
        }
    }

    protected void analyzeDatafieldElement(Element element) {
        analyzeElement(element, "072", new char[]{'a', 'x'}, datafield072);
        analyzeElement(element, "533", new char[]{'a'}, datafield533);
        analyzeElement(element, "650", new char[]{'a'}, datafield650);
        analyzeElement(element, "651", new char[]{'a'}, datafield651);
        analyzeElement(element, "655", new char[]{'a'}, datafield655);
        analyzeElement(element, "856", new char[]{'u', 'x'}, datafield856);
        analyzeElement(element, "910", new char[]{'b'}, datafield910);
    }

    protected void analyzeElement(Element element, String tag, char[] subfields, MultipleDataField multipleDatafield) {
        if (tag.equals(element.attributeValue("tag"))) {
            DataField datafield = new DataField(subfields.length);
            fillDataField(element, subfields, datafield);
            multipleDatafield.addDataField(datafield);
        }
    }

    protected void analyzeElement(Element element, String tag, char[] subfields, DataField datafield) {
        if (tag.equals(element.attributeValue("tag"))) {
            fillDataField(element, subfields, datafield);
        }
    }

    private void fillDataField(Element element, char[] subfields, DataField datafield) {
        for (Iterator<Element> i2 = element.elementIterator(); i2.hasNext();) {
            Element subfieldElement = (Element) i2.next();
            for (int i = 0; i < subfields.length; i++) {
                char code = subfields[i];
                String codeStr = String.valueOf(code);
                if (codeStr.equals((subfieldElement.attributeValue("code")))) {
                    datafield.setSubfield(code, subfieldElement.getText());
                }
            }
        }
    }

    /**
     * @return the controlfieldFMT
     */
    public String getControlfieldFMT() {
        return controlfieldFMT;
    }

    public DataField getDatafield910() {
        return datafield910;
    }

    public MultipleDataField getDatafield072() {
        return datafield072;
    }

    public MultipleDataField getDatafield533() {
        return datafield533;
    }

    public MultipleDataField getDatafield651() {
        return datafield651;
    }

    public MultipleDataField getDatafield650() {
        return datafield650;
    }

    public MultipleDataField getDatafield655() {
        return datafield655;
    }

    public MultipleDataField getDatafield856() {
        return datafield856;
    }
}
